﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Collections;
using static LightCAD.MathLib.Constants;
using LightCAD.MathLib;

namespace LightCAD.Three
{
    public static class AnimationUtils
    {

        public static KeyframeTrack CreateKeyframeTrack(string typeName, string name, double[] times, double[] values, int? interpolation = null)
        {
            KeyframeTrack track = null;
            if (typeName == "BooleanKeyframeTrack")
                track = new BooleanKeyframeTrack(name, times, values, interpolation);
            else if (typeName == "ColorKeyframeTrack")
                track = new ColorKeyframeTrack(name, times, values, interpolation);
            else if (typeName == "NumberKeyframeTrack")
                track = new NumberKeyframeTrack(name, times, values, interpolation);
            else if (typeName == "QuaternionKeyframeTrack")
                track = new QuaternionKeyframeTrack(name, times, values, interpolation);
            else if (typeName == "StringKeyframeTrack")
                track = new StringKeyframeTrack(name, times, values, interpolation);
            else if (typeName == "VectorKeyframeTrack")
                track = new VectorKeyframeTrack(name, times, values, interpolation);
            return track;
        }
        // same as Array.prototype.slice, but also works on typed arrays
        public static ListEx<T> arraySlice<T>(ListEx<T> array, int from = -1, int to = -1)
        {
            if (from < 0 && to < 0)
                return array.Slice();
            else if (to < 0)
                return array.Slice(from);
            else
                return array.Slice(from, to);
        }
        public static T[] arraySlice<T>(T[] array, int from = -1, int to = -1)
        {
            return arraySlice(array.ToListEx(), from, to).ToArray();
        }
        // converts an array to a specific type
        public static ListEx<double> convertArray(ListEx<object> array, string type)
        {
            if (array == null) return null;

            var newArr = new ListEx<double>(array.Length);
            for (var i = 0; i < array.Length; i++)
            {
                newArr[i] = (double)array[i];
            }
            return newArr; // create Array

        }

        //public static JArray toJArray(IList<double> arr)
        //{
        //    var jarr = new JArray();
        //    foreach (var e in arr)
        //    {
        //        jarr.Add(e);
        //    }
        //    return jarr;
        //}
        //public static JsArr<double> fromJArray(JArray jarr)
        //{
        //    var arr = new JsArr<double>();
        //    foreach (var e in jarr) arr.push((double)e);
        //    return arr;
        //}


        // returns an array by which times and values can be sorted
        public static ListEx<int> getKeyframeOrder(ListEx<double> times)
        {

            int compareTime(int i, int j)
            {
                if (times[i] == times[j]) return 0;

                return times[i] - times[j] > 0 ? 1 : -1;

            }

            var n = times.Length;
            var result = new ListEx<int>(n);
            for (var i = 0; i != n; ++i) result[i] = i;

            result.Sort(compareTime);

            return result;

        }

        // uses the array previously returned by 'getKeyframeOrder' to sort data
        public static ListEx<T> sortedArray<T>(ListEx<T> values, int stride, ListEx<int> order)
        {

            var nValues = values.Length;
            var result = new ListEx<T>(nValues);

            for (int i = 0, dstOffset = 0; dstOffset != nValues; ++i)
            {

                var srcOffset = order[i] * stride;

                for (int j = 0; j != stride; ++j)
                {

                    result[dstOffset++] = values[srcOffset + j];

                }

            }

            return result;

        }

        // function for parsing AOS keyframe formats
        //public static void flattenJSON(JArray jsonKeys, JsArr<double> times, JsArr<double> values, string valuePropertyName)
        //{

        //    var i = 1;
        //    var key = (JObject)jsonKeys[0];

        //    while (key != null && key[valuePropertyName] == null)
        //    {

        //        key = (JObject)jsonKeys[i++];

        //    }

        //    if (key == null) return; // no data

        //    var value = key[valuePropertyName];
        //    if (value == null) return; // no data

        //    if (value is JArray)
        //    {

        //        do
        //        {

        //            value = key[valuePropertyName];

        //            if (value != null)
        //            {

        //                times.push((double)key["time"]);
        //                var jarr = value as JArray;
        //                foreach (var item in jarr)
        //                    values.push((double)item);

        //            }

        //            key = (JObject)jsonKeys[i++];

        //        } while (key != null);

        //    }
        //    else if (value["toArray"] != null)
        //    {

        //        // ...assume THREE.Math-ish

        //        do
        //        {

        //            value = key[valuePropertyName];

        //            if (value != null)
        //            {

        //                times.push((double)key["time"]);
        //                //value.toArray(values, values.length);不知如何处理，json里包含了方法？

        //            }

        //            key = (JObject)jsonKeys[i++];

        //        } while (key != null);

        //    }
        //    else
        //    {

        //        // otherwise push as-is

        //        do
        //        {

        //            value = key[valuePropertyName];

        //            if (value != null)
        //            {

        //                times.push((double)key["time"]);
        //                values.push((double)value);

        //            }

        //            key = (JObject)jsonKeys[i++];
        //        } while (key != null);
        //    }
        //}

        internal static ListEx<double> listToDoubleArray(IList buffer)
        {
            var arr = new ListEx<double>(buffer.Count);
            for (int i = 0; i < buffer.Count; i++)
            {
                arr[i] = (double)buffer[i];
            }
            return arr;
        }

        public static object subclip(AnimationClip sourceClip, string name, int startFrame, int endFrame, int fps = 30)
        {

            var clip = sourceClip.clone();

            clip.name = name;

            var tracks = new ListEx<KeyframeTrack>();

            for (int i = 0; i < clip.tracks.Length; ++i)
            {

                var track = clip.tracks[i];
                var valueSize = track.getValueSize();

                var times = new ListEx<double>();
                var values = new ListEx<double>();

                for (int j = 0; j < track.times.Length; ++j)
                {

                    var frame = track.times[j] * fps;

                    if (frame < startFrame || frame >= endFrame) continue;

                    times.Push(track.times[j]);

                    for (int k = 0; k < valueSize; ++k)
                    {

                        values.Push(track.values[j * valueSize + k]);

                    }

                }

                if (times.Length == 0) continue;

                track.times = times.ToArray();
                track.values = values.ToArray();

                tracks.Push(track);

            }

            clip.tracks = tracks;

            // find minimum .times value across all tracks in the trimmed clip

            var minStartTime = double.PositiveInfinity;

            for (var i = 0; i < clip.tracks.Length; ++i)
            {

                if (minStartTime > clip.tracks[i].times[0])
                {

                    minStartTime = clip.tracks[i].times[0];

                }

            }

            // shift all tracks such that clip begins at t=0

            for (var i = 0; i < clip.tracks.Length; ++i)
            {

                clip.tracks[i].shift(-1 * minStartTime);

            }

            clip.resetDuration();

            return clip;

        }

        public static AnimationClip makeClipAdditive(AnimationClip targetClip, int referenceFrame = 0, AnimationClip referenceClip = null, int fps = 30)
        {
            if (referenceClip == null) referenceClip = targetClip;
            if (fps <= 0) fps = 30;

            var numTracks = referenceClip.tracks.Length;
            var referenceTime = referenceFrame / fps;

            // Make each track's values relative to the values at the reference frame
            for (var i = 0; i < numTracks; ++i)
            {

                var referenceTrack = referenceClip.tracks[i];
                var referenceTrackType = referenceTrack.ValueTypeName;

                // Skip this track if it's non-numeric
                if (referenceTrackType == "bool" || referenceTrackType == "string") continue;

                // Find the track in the target clip whose name and type matches the reference track
                var targetTrack = targetClip.tracks.Find((track) =>
                {

                    return track.name == referenceTrack.name
                        && track.ValueTypeName == referenceTrackType;

                });

                if (targetTrack == null) continue;

                var referenceOffset = 0;
                var referenceValueSize = referenceTrack.getValueSize();

                //TODO:::???需要调整处理方案
                //if (referenceTrack.createInterpolant is InterpolantFactoryMethodGLTFCubicSpline)
                //{

                //    referenceOffset = referenceValueSize / 3;

                //}

                var targetOffset = 0;
                var targetValueSize = targetTrack.getValueSize();

                //if (targetTrack.createInterpolant.is InterpolantFactoryMethodGLTFCubicSpline)
                //{

                //    targetOffset = targetValueSize / 3;

                //}

                var lastIndex = referenceTrack.times.Length - 1;
                double[] referenceValue;

                // Find the value to subtract out of the track
                if (referenceTime <= referenceTrack.times[0])
                {

                    // Reference frame is earlier than the first keyframe, so just use the first keyframe
                    var startIndex = referenceOffset;
                    var endIndex = referenceValueSize - referenceOffset;
                    referenceValue = arraySlice(referenceTrack.values, startIndex, endIndex);

                }
                else if (referenceTime >= referenceTrack.times[lastIndex])
                {

                    // Reference frame is after the last keyframe, so just use the last keyframe
                    var startIndex = lastIndex * referenceValueSize + referenceOffset;
                    var endIndex = startIndex + referenceValueSize - referenceOffset;
                    referenceValue = arraySlice(referenceTrack.values, startIndex, endIndex);

                }
                else
                {

                    // Interpolate to the reference value
                    var interpolant = referenceTrack.createInterpolant(null);
                    var startIndex = referenceOffset;
                    var endIndex = referenceValueSize - referenceOffset;
                    interpolant.Evaluate(referenceTime);
                    referenceValue = arraySlice(interpolant.ResultBuffer, startIndex, endIndex);

                }

                // Conjugate the quaternion
                if (referenceTrackType == "quaternion")
                {

                    var referenceQuat = new Quaternion().FromArray(referenceValue.ToArray()).Normalize().Conjugate();
                    referenceQuat.ToArray(referenceValue.ToArray());

                }

                // Subtract the reference value from all of the track values

                var numTimes = targetTrack.times.Length;
                for (var j = 0; j < numTimes; ++j)
                {

                    var valueStart = j * targetValueSize + targetOffset;

                    if (referenceTrackType == "quaternion")
                    {

                        // Multiply the conjugate for quaternion track types
                        Quaternion.MultiplyQuaternionsFlat(
                            targetTrack.values.ToArray(),
                            valueStart,
                            referenceValue.ToArray(),
                            0,
                            targetTrack.values.ToArray(),
                            valueStart
                        );

                    }
                    else
                    {

                        var valueEnd = targetValueSize - targetOffset * 2;

                        // Subtract each value for all other numeric track types
                        for (var k = 0; k < valueEnd; ++k)
                        {

                            targetTrack.values[valueStart + k] -= referenceValue[k];

                        }

                    }

                }

            }

            targetClip.blendMode = AdditiveAnimationBlendMode;

            return targetClip;

        }

    }

}
